home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Pascal / Book Demos in Pascal / SpriteEngine / SpriteTools.p < prev   
Text File  |  1995-04-04  |  11KB  |  388 lines

  1. { SpriteTools}
  2. { Routines to be called from the engine and from SpriteHandlers}
  3.  
  4. unit SpriteTools;
  5.  
  6. interface
  7.     uses
  8. {$IFC UNDEFINED THINK_PASCAL}
  9.         Types, QuickDraw, Fonts, Events, Packages, Menus, Dialogs, Windows,{}
  10.         OSUtils, ToolUtils, OSEvents, Memory,
  11. {$ENDC}
  12.         QDOffScreen, SpriteStructure;
  13.  
  14. {MyNewGWorld: Creates a GWorld}
  15. {LoadFaceFromCicn: Loads a face}
  16. {PlotFace: Draws a face}
  17.  
  18. {NewSprite: Creates a sprite}
  19. {DisposeSprite: Disposes a sprite}
  20.  
  21. {KeepOnScreen: Performs border checks for a sprite}
  22. {RectSeparate: Moves two sprites apart}
  23.  
  24.  
  25. { Delay constant}
  26.     const
  27.         kFrameTime = 1;
  28.  
  29. (* The window pointer *)
  30.     var
  31.         myWindow: WindowPtr;
  32.  
  33. (****************************************)
  34. (* Global variables for sprite handling *)
  35. (****************************************)
  36.  
  37. { EntityType and SpriteRecord are defined in SpriteHandlers.h}
  38.  
  39. (* A global pointer is the root of the entity list *)
  40.         gSpriteList: SpritePtr;
  41.  
  42. (* GWorlds for the animation and background buffers *)
  43.         gOffScreen, gBackScreen: GrafPtr;
  44.  
  45. (*** End of sprite handling variables ***)
  46.  
  47. { Routines in SpriteTools.c}
  48.     procedure MyNewGWorld (var offscreenGWorld: GrafPtr; var boundsRect: Rect);
  49.     function LoadFaceFromCicn (cicnId: Integer): GrafPtr;
  50.     procedure PlotFace (theCicn: GrafPtr; destPort: GrafPtr; where: Point);
  51.  
  52. (*Sprite list management*)
  53.     function NewSprite: SpritePtr;
  54.     procedure DisposeSprite (who: SpritePtr);
  55.  
  56. (*Sprite utilities*)
  57.     function KeepOnScreen (theSprite: SpritePtr): Boolean;
  58.     function KeepOnScreenFixed (theSprite: SpritePtr): Boolean;
  59.     function RectSeparate (theSprite: SpritePtr; anotherSprite: SpritePtr): Integer;
  60.     function Rand (range: Integer): Integer;
  61.     function RegionHit (theSprite: SpritePtr; anotherSprite: SpritePtr): Boolean;
  62.     procedure SplitVector (v: Point; d: Point; var p: Point; var n: Point);
  63.  
  64.  
  65. implementation
  66.  
  67.  
  68.     procedure DoError;
  69.     begin
  70.         SysBeep(1);
  71.         ExitToShell;
  72.     end;
  73.  
  74. (*MyNewGWorld: Glue to NewGWorld*)
  75. (*I declare offscreenGWorld as GrafPtr to save us a bunch of typecasts later (in CopyBits).*)
  76. (*Most parameters to NewGWorld omitted - NewGWorld is smart enough to make the defaults useable.*)
  77.  
  78.     procedure MyNewGWorld (var offscreenGWorld: GrafPtr; var boundsRect: Rect);
  79.         var
  80.             saveGD: GDHandle;
  81.             savePort: GWorldPtr;
  82.     begin
  83.         GetGWorld(savePort, saveGD);
  84.  
  85.         if noErr <> NewGWorld(GWorldPtr(offscreenGWorld), 0, boundsRect, nil, nil, [pixelsLocked]) then
  86.             DoError;
  87. (*We lock the offscreen pixmap so we can CopyBits and PlotCIcon to it.*)
  88.         if LockPixels(CGrafPtr(offscreenGWorld)^.portPixMap) then
  89.             ;
  90. (*Note: We should unlock it (UnlockPixels) when not animating, to avoid memory fragmentation,*)
  91. (*but you can bother with that later if it's a problem.*)
  92.         SetGWorld(savePort, saveGD);
  93.     end; (*MyNewGWorld*)
  94.  
  95.  
  96.  
  97.     function LoadFaceFromCicn (cicnId: Integer): GrafPtr;
  98.         var
  99.             offscreenGWorld: GrafPtr;
  100.             theCicn: CIconHandle;
  101.             saveGD: GDHandle;
  102.             savePort: GWorldPtr;
  103.     begin
  104.         GetGWorld(savePort, saveGD);
  105.         theCicn := GetCIcon(cicnId);
  106.         MyNewGWorld(offscreenGWorld, theCicn^^.iconMask.bounds);
  107.         if offscreenGWorld <> nil then
  108.  
  109.             begin
  110.                 SetGWorld(GWorldPtr(offscreenGWorld), nil);
  111.                 PlotCIcon(theCicn^^.iconMask.bounds, theCicn);
  112.  
  113. (*I use the clipRgn for storing the mask region. This may seem dangerous,}
  114. {but when we aren't drawing in the GWorld anyway, it won't matter.*)
  115.                 if offscreenGWorld = nil then
  116.                     offscreenGWorld^.clipRgn := NewRgn;
  117.                 if (noErr <> BitMapToRegion(offscreenGWorld^.clipRgn, theCicn^^.iconMask)) then(**)
  118.                     offscreenGWorld^.clipRgn := nil;(*or DisposeRgn?*)
  119.  
  120.                 DisposeCIcon(theCicn);
  121.             end;
  122.         SetGWorld(savePort, saveGD);
  123.         LoadFaceFromCicn := offscreenGWorld;
  124.     end; (*LoadFaceFromCicn*)
  125.  
  126.  
  127.     var
  128.         gTmpRgn: RgnHandle;
  129.  
  130.     procedure PlotFace (theCicn: GrafPtr; destPort: GrafPtr; where: Point);
  131.         var
  132.             saveGD: GDHandle;
  133.             savePort: GWorldPtr;
  134.             bounds: Rect;
  135.             saveForeColor, saveBackColor: RGBColor;
  136.     begin
  137.         GetGWorld(savePort, saveGD);
  138.         bounds := theCicn^.portRect;
  139.         OffsetRect(bounds, where.h - bounds.left, where.v - bounds.top);
  140.  
  141.         if gTmpRgn = nil then
  142.             gTmpRgn := NewRgn; (*For top speed, we make this global, and create it only once!*)
  143.         CopyRgn(theCicn^.clipRgn, gTmpRgn);
  144.         OffsetRgn(gTmpRgn, where.h, where.v);
  145.         SetPort(destPort);  (*I assume that the device is correctly set.*)
  146.         GetForeColor(saveForeColor);
  147.         GetBackColor(saveBackColor);
  148.         ForeColor(blackColor);
  149.         BackColor(whiteColor);
  150.         CopyBits(theCicn^.portBits, destPort^.portBits, theCicn^.portRect, bounds, srcCopy, gTmpRgn);
  151.         RGBForeColor(saveForeColor);
  152.         RGBBackColor(saveBackColor);
  153.         SetGWorld(savePort, saveGD);
  154.     end; (*PlotFace*)
  155.  
  156.  
  157. (*************************************)
  158. (* Routines for sprite list handling *)
  159. (*************************************)
  160.  
  161.  
  162. (* NewSprite allocates space for a new entity and puts it in the entity list *)
  163.  
  164.     function NewSprite: SpritePtr;
  165.         var
  166.             who: SpritePtr;
  167.     begin
  168.         who := SpritePtr(NewPtr(sizeof(SpriteRecord)));
  169.         if who = nil then
  170.             begin
  171.                 NewSprite := nil;
  172.                 exit(NewSprite);
  173.             end;
  174.         if gSpriteList <> nil then
  175.             begin
  176.                 gSpriteList^.prev := who;
  177.             end;
  178.         who^.next := gSpriteList;
  179.         who^.prev := nil;
  180.         gSpriteList := who;
  181.         NewSprite := who;
  182.     end; (*NewSprite*)
  183.  
  184.  
  185. (* DisposeSprite removes an entity from the list and disposes it. *)
  186.  
  187.     procedure DisposeSprite (who: SpritePtr);
  188.     begin
  189.         if who = nil then
  190.             exit(DisposeSprite);
  191.         if (who^.next <> nil) then
  192.             who^.next^.prev := who^.prev;
  193.         if (who^.prev <> nil) then
  194.             who^.prev^.next := who^.next;
  195.         if (who = gSpriteList) then
  196.             gSpriteList := who^.next;
  197.         DisposePtr(Ptr(who));
  198.     end; (*DisposeSprite*)
  199.  
  200.  
  201. (*** End of sprite handling routines ***)
  202.  
  203.  
  204. (* KeepOnScreen makes border checks to keep the sprite within the window.}
  205. {on a border hit, the speed is negated in order to make the sprite bounce.}
  206. {KeepOnScreen returns true if a border was hit. *)
  207.  
  208.     function KeepOnScreen (theSprite: SpritePtr): Boolean;
  209.         var
  210.             returnValue: Boolean;
  211.     begin
  212.         returnValue := false;
  213.         if theSprite^.position.h < 0 then
  214.             begin
  215.                 theSprite^.position.h := 0;
  216.                 theSprite^.speed.h := abs(theSprite^.speed.h);
  217.                 returnValue := true;
  218.             end;
  219.         if theSprite^.position.v < 0 then
  220.             begin
  221.                 theSprite^.position.v := 0;
  222.                 theSprite^.speed.v := abs(theSprite^.speed.v);
  223.                 returnValue := true;
  224.             end;
  225.         if theSprite^.position.h > gOffScreen^.portRect.right - theSprite^.face^.portRect.right then
  226.             begin
  227.                 theSprite^.position.h := gOffScreen^.portRect.right - theSprite^.face^.portRect.right;
  228.                 theSprite^.speed.h := -abs(theSprite^.speed.h);
  229.                 returnValue := true;
  230.             end;
  231.         if theSprite^.position.v > gOffScreen^.portRect.bottom - theSprite^.face^.portRect.bottom then
  232.             begin
  233.                 theSprite^.position.v := gOffScreen^.portRect.bottom - theSprite^.face^.portRect.bottom;
  234.                 theSprite^.speed.v := -abs(theSprite^.speed.v);
  235.                 returnValue := true;
  236.             end;
  237.  
  238.         KeepOnScreen := returnValue;
  239.     end; (*KeepOnScreen*)
  240.  
  241.  
  242. {$ifc _hasfixedpoint}
  243. (*Same as above, but also modifies the fixedPointPosition field*)
  244.     function KeepOnScreenFixed (theSprite: SpritePtr): Boolean;
  245.         var
  246.             returnValue: Boolean;
  247.     begin
  248.         returnValue := false;
  249.         if theSprite^.fixedPointPosition.h < 0 then
  250.             begin
  251.                 theSprite^.position.h := 0;
  252.                 theSprite^.fixedPointPosition.h := 0;
  253.                 theSprite^.speed.h := abs(theSprite^.speed.h);
  254.                 returnValue := true;
  255.             end;
  256.         if (theSprite^.fixedPointPosition.v < 0) then
  257.             begin
  258.                 theSprite^.position.v := 0;
  259.                 theSprite^.fixedPointPosition.v := 0;
  260.                 theSprite^.speed.v := abs(theSprite^.speed.v);
  261.                 returnValue := true;
  262.             end;
  263.         if (theSprite^.position.h > gOffScreen^.portRect.right - theSprite^.face^.portRect.right) then
  264.             begin
  265.                 theSprite^.position.h := gOffScreen^.portRect.right - theSprite^.face^.portRect.right;
  266.                 theSprite^.fixedPointPosition.h := BSL(theSprite^.position.h, 4);
  267.                 theSprite^.speed.h := -abs(theSprite^.speed.h);
  268.                 returnValue := true;
  269.             end;
  270.         if (theSprite^.position.v > gOffScreen^.portRect.bottom - theSprite^.face^.portRect.bottom) then
  271.             begin
  272.                 theSprite^.position.v := gOffScreen^.portRect.bottom - theSprite^.face^.portRect.bottom;
  273.                 theSprite^.fixedPointPosition.v := BSL(theSprite^.position.v, 4);
  274.                 theSprite^.speed.v := -abs(theSprite^.speed.v);
  275.                 returnValue := true;
  276.             end;
  277.  
  278.         KeepOnScreenFixed := returnValue
  279.     end; (*KeepOnScreenFixed*)
  280. {$endc}
  281.  
  282.  
  283. (* Moves two sprites apart, to separate them with respect to their bounding boxes. *)
  284.  
  285.     function RectSeparate (theSprite: SpritePtr; anotherSprite: SpritePtr): Integer;
  286.  
  287.         var
  288.             distance: array[0..3] of Integer;
  289.             shortest, shortestDistance, i: Integer;
  290.             bounds1, bounds2: Rect;
  291.  
  292.     begin
  293.         bounds1 := theSprite^.face^.portRect;
  294.         OffsetRect(bounds1, theSprite^.position.h, theSprite^.position.v);
  295.  
  296.         bounds2 := anotherSprite^.face^.portRect;
  297.         OffsetRect(bounds2, anotherSprite^.position.h, anotherSprite^.position.v);
  298.  
  299. (*Calculate the distance to separate the sprites in every direction*)
  300.         distance[0] := bounds2.top - bounds1.bottom; {up}
  301.         distance[1] := bounds2.bottom - bounds1.top; {down}
  302.         distance[2] := bounds2.right - bounds1.left; {right}
  303.         distance[3] := bounds2.left - bounds1.right; {left}
  304.  
  305. (*Find the shortest distance*)
  306.         shortest := 0;
  307.         shortestDistance := abs(distance[0]);
  308.         for i := 1 to 3 do
  309.             if abs(distance[i]) < shortestDistance then
  310.                 begin
  311.                     shortest := i;
  312.                     shortestDistance := abs(distance[i]);
  313.                 end;
  314.  
  315. (*Move the sprite in the appropriate direction*)
  316.         case shortest of
  317.             0, 1: 
  318.                 theSprite^.position.v := theSprite^.position.v + distance[shortest];
  319.             2, 3: 
  320.                 theSprite^.position.h := theSprite^.position.h + distance[shortest];
  321.         end; {case}
  322.         RectSeparate := shortest;
  323.     end; (*RectSeparate*)
  324.  
  325.  
  326. (* Random number from 0 to range-1 *)
  327.  
  328.     function Rand (range: Integer): Integer;
  329.  
  330.         var
  331.             roll: Integer;
  332.  
  333.     begin
  334.         roll := Random;
  335.         Rand := abs(roll) mod range;
  336.     end; (*Rand*)
  337.  
  338.  
  339.  
  340. (* Collision test using regions! *)
  341.  
  342.     function RegionHit (theSprite: SpritePtr; anotherSprite: SpritePtr): Boolean;
  343.  
  344.         var
  345.             faceRegion1, faceRegion2: RgnHandle;
  346.             result: Boolean;
  347.  
  348.     begin
  349.         faceRegion1 := NewRgn;
  350.         faceRegion2 := NewRgn;
  351.  
  352.         CopyRgn(theSprite^.face^.clipRgn, faceRegion1);
  353.         OffsetRgn(faceRegion1, theSprite^.position.h, theSprite^.position.v);
  354.  
  355.         CopyRgn(anotherSprite^.face^.clipRgn, faceRegion2);
  356.         OffsetRgn(faceRegion2, anotherSprite^.position.h, anotherSprite^.position.v);
  357.  
  358.         SectRgn(faceRegion1, faceRegion2, faceRegion1);
  359.         result := not EmptyRgn(faceRegion1);
  360.  
  361.         DisposeRgn(faceRegion1);
  362.         DisposeRgn(faceRegion2);
  363.  
  364.         RegionHit := result;
  365.     end; (*RegionHit*)
  366.  
  367.  
  368. (* Split a vector v into a component p parallel to another vector d,}
  369. {and a compionent n that is perpendicular to d. Useful for realistic}
  370. {collision handling! *)
  371.  
  372.     procedure SplitVector (v: Point; d: Point; var p: Point; var n: Point);
  373.  
  374.         var
  375.             length2, dotProduct: LongInt;
  376.  
  377.     begin
  378.         length2 := d.h * d.h + d.v * d.v;    (*Squared length of "d"*)
  379.  
  380.         dotProduct := v.h * d.h + v.v * d.v;    (*Scalar product*)
  381.  
  382.         p.h := d.h * dotProduct div length2;
  383.         p.v := d.v * dotProduct div length2;
  384.         n.h := v.h - p.h;
  385.         n.v := v.v - p.v;
  386.     end; (* SplitVector *)
  387.  
  388. end.